package edu.siu.math.egut.util;

/**
 * @author josephhundley
 *
 */
public class Matrix {

    private static final String DEFAULT_OUTPUT_MODE = "screen";
    private final int size;
    private final Polynomial [][] entries;
    
    Matrix(final int givenSize, final Polynomial[][] givenEntries){
	size = givenSize;
	entries = new Polynomial[size][size];
	for( int i = 0; i < size; i++){
	    for(int j = 0 ; j < size; j++){
		entries[i][j]= givenEntries[i][j];
	    }
	}
	
    }
    
    /**
     * @param suni
     * @param vRoots
     * @param g
     * @return
     * @throws NotARootException
     */
    public static Matrix matrixOf(final Sunipotent suni, final int[][] vRoots, final Group g) throws NotARootException {
	return x(suni.getRoot(), suni.getArgument(), vRoots, g);
    
    }

    /**
     * @param a
     * @return
     * @throws MatrixMultiplicationException 
     */
    public Matrix times(final Matrix a) throws MatrixMultiplicationException{
	if(!(size== a.size))
	{
	    throw new MatrixMultiplicationException();
	}
	final Polynomial[][] answer = new Polynomial[size][size];
	for( int i = 0; i < size; i++)
	    for( int j = 0 ; j < size ; j++){
		Polynomial entry = Polynomial.create(0);
		for( int k = 0; k< size; k++){
		    entry = entry.plus(entries[i][k].times(a.entries[k][j]));
		}
		answer[i][j]=entry;
	    }
	return new Matrix(size, answer);
    }
    
    /**
     * Take G a split simple connected simply connected exceptional group, write Lie(G) for the Lie algebra.
     * Take alpha a root of G.
     * Take S a set of roots of G, and let V be the subspace of Lie(G) spanned by the corresponding one dimensional subalgebras.
     * Take r a polynomial.
     * Assume that conjugation by the simple unipotent x_\alpha(r) preserves V (i.e., that for each root beta in S, either alpha+beta 
     * is a non-root, or else it is again in S.
     * Then we compute the matrix of x_alpha(r), regarded as a linear automorphism of V, with respect to the basis for V determined by 
     * our chosen realization. 
     * 
     * 
     * FIXME current implementation does not check that V is preserved by the action of x_alpha(r)
     * 
     * @param alpha the root
     * @param r the polynomial
     * @param vRoots the list of roots determining our subspace of Lie(G)
     * @param g the group 
     * @return the matrix for x_alpha(r), regarded as a linear automorphism of V, with respect to the basis for V determined by our chosen realization. 
     * @throws NotARootException if alpha or any element of vRoots is not a root of g.
     */
    public static Matrix x(final int[] alpha, final Polynomial r, final int[][] vRoots, final Group g ) throws NotARootException{
	final VectorCayleyTable sums = g.getVectorCayleyTable();
	if(! g.isRoot(alpha))
	    throw new NotARootException();
	final int rank = vRoots.length;
	final Polynomial [][] entries = new Polynomial[rank][rank];
	
	// initialize to identity matrix
	for(int i = 0; i < rank; i++)
	    for(int j = 0 ; j < rank ; j++)
		entries[i][j] = Polynomial.create(i==j?1:0); 
	
	for( int i = 0; i < vRoots.length; i++){
	    if(!g.isRoot(vRoots[i]))
		throw new NotARootException();
	    int [] beta = vRoots[i];
	    int n = 1;
	    int coefficient = 1;
	    Polynomial pow =Polynomial.create(1);
	    int [] sum = sums.add(beta, alpha);
	    while( sum != null){
		pow = pow.times(r);
		coefficient = coefficient * g.getStructureConstant(alpha, beta)/n;
		n=n+1;
		final int j = Algorithms.indexOf(vRoots,sum);
		if(j == -1 ){
		    System.out.print("Error.  Possible attempt to act by an element which does not preserve the given space.");
		}
		entries[j][i] = pow.times(coefficient);
		beta = sum;
		sum = sums.add(beta, alpha);
		
	    }
	}
	return new Matrix(rank, entries);
    }
    
    /**
     * Take G a split simple connected simply connected exceptional group, write Lie(G) for the Lie algebra.
     * Take 1 \le i \le ssRank(G)
     * Take S a set of roots of G, and let V be the subspace of Lie(G) spanned by the corresponding one dimensional subalgebras.
     * Assume that conjugation by the ith simple reflection s_i preserves V (i.e., that for each root beta in S,  s_i.beta 
     *  is again in S.
     * Then we compute the matrix of s_i, regarded as a linear automorphism of V, with respect to the basis for V determined by 
     * our chosen realization. 
     * 
     * FIXME current implementation is probably incredibly wasteful
     * 
     * @param i number of simple reflection
     * @param vRoots the list of roots determining our subspace of Lie(G)
     * @param g the group 
     * @return the matrix for s_i, regarded as a linear automorphism of V, with respect to the basis for V determined by our chosen realization. 
     * @throws NotARootException if alpha or any element of vRoots is not a root of g.
     * @throws MatrixMultiplicationException 
     */
    public static Matrix s(final int i , final int[][] vRoots, final Group g) throws NotARootException, MatrixMultiplicationException{
	final int[] ai = g.getRoots()[i-1];
	return x(ai, Polynomial.create(1), vRoots,g).times(x(g.negative(ai), Polynomial.create(-1), vRoots, g )).times(x(ai, Polynomial.create(1), vRoots,g));
    }
    
    /**
     * @return
     */
    public String toLaTeX(){
	final StringBuilder sb = new StringBuilder("\\begin{bmatrix}");
	for(int i = 0 ; i < size ; i++)
	    for( int j = 0 ; j < size; j++ ){
		sb.append(entries[i][j].toString());
		sb.append((j==size-1)?"\\\\":"&");
	    }
	sb.delete(sb.length()-1, sb.length());
	sb.append("end{bmatrix}");
	return sb.toString();
    }
    /**
     * Output the matrix as a comma-separated array with braces enclosing each row and the whole.
     * (I.e., in a format that can be read by Mathematica.)
     * @return a string representation of the matrix that can be pasted into Mathematica
     */
    public String toMathematica(){
	final StringBuilder sb = new StringBuilder("{{");
	for(int i = 0 ; i < size ; i++)
	    for( int j = 0 ; j < size; j++ ){
		sb.append(entries[i][j].toString());
		sb.append((j==size-1)?"},{":",");
	    }
	sb.delete(sb.length()-2, sb.length());
	sb.append("}");
	return sb.toString();
	// FIXME implement some formatting commands which will modify non-mathematica-readable
	// variable names
    }

    /**
     * @param length
     * @return
     */
    public static Matrix id(final int length) {
	final Polynomial[][] entries = new Polynomial[length][length];
	for( int i = 0; i < length ; i++)
	    for( int j = 0 ; j < length; j++)
		entries[i][j] = Polynomial.create( i==j?1:0);
	return new Matrix( length, entries);
	
    }

    /**
     * @param string
     * @param vRoots
     * @param g
     * @return
     * @throws NotARootException
     * @throws NoSuchElementException
     * @throws MatrixMultiplicationException 
     */
    public static Matrix matrixOfWeylWord(String string, final int[][] vRoots, final Group g) throws NotARootException, NoSuchElementException, MatrixMultiplicationException {
	string = edu.siu.math.egut.io.Algorithms.removeWhiteSpaceCommasAndBrackets(string);
	if(!Algorithms.representsWeylWord(string, g.getRank()))
	    throw new NoSuchElementException();
	Matrix answer = Matrix.id(vRoots.length);
	for( int i = 0; i < string.length(); i++)
	    answer = answer.times(s(string.charAt(i)-48, vRoots, g));
	return answer;
    }

    public String toString(final String string) {
	if(string.matches("mca")||string.matches("Mathematica"))
	    return this.toMathematica();
	if(string.matches("tex"))
	    return this.toLaTeX();
	if(string.matches("screen")||string.matches("scr"))
	    return this.toScreen();
	return this.toString();
    }
    public String toScreen(){
	final StringBuilder sb = new StringBuilder();
	int[] colWidths = new int[size];
	for(int j = 0; j < size ; j++){
	    for(int i = 0; i < this.size; i++)
		colWidths[j]=Math.max(colWidths[j], entries[i][j].toString().length()+1);
	}
	for(int i =0; i < size; i++){
	    for( int j = 0 ; j < size; j++)
		sb.append(String.format("%1$-"+colWidths[j]+"s", entries[i][j].toString()));
	    sb.append("\n");
	}
		
	return sb.toString();
    }

    public String toString(){
	return this.toString(DEFAULT_OUTPUT_MODE);
    }

    public Matrix transpose() {
	Polynomial[][] newEntries = new Polynomial[size][size];
	for( int i = 0; i < size; i++)
	    for(int j=0; j<size; j++)
		newEntries[i][j]=entries[j][i];
	return new Matrix(size, newEntries);
    
    }
}
